package io.gitee.mingbaobaba.apijson.querycondition.query.dao.support;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.gitee.mingbaobaba.apijson.querycondition.query.exception.ConditionException;
import io.gitee.mingbaobaba.apijson.querycondition.query.properties.ApiJsonQueryProperties;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;

/**
 * <p>默认 Token Provider，支持本地缓存</p>
 */
@Slf4j
public class CachingAccessTokenProvider implements AccessTokenProvider {

    private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");

    private final ApiJsonQueryProperties properties;
    private final OkHttpClient okHttpClient;
    private final Random random = new Random();
    private final Object tokenLock = new Object();

    private volatile TokenContext cachedToken;

    public CachingAccessTokenProvider(ApiJsonQueryProperties properties, OkHttpClient okHttpClient) {
        this.properties = Objects.requireNonNull(properties, "ApiJsonQueryProperties不能为空");
        this.okHttpClient = Objects.requireNonNull(okHttpClient, "OkHttpClient不能为空");
    }

    @Override
    public String getBearerToken() {
        if (!properties.isEnableTokenCache()) {
            return requestToken().getAccessToken();
        }
        TokenContext context = cachedToken;
        if (context != null && context.isValid()) {
            return context.getAccessToken();
        }
        synchronized (tokenLock) {
            context = cachedToken;
            if (context == null || !context.isValid()) {
                context = requestToken();
                cachedToken = context;
            }
        }
        return context.getAccessToken();
    }

    private TokenContext requestToken() {
        Map<String, Object> params = new HashMap<>(4);
        params.put("client_id", properties.getClientId());
        params.put("client_secret", properties.getClientSecret());
        params.put("grant_type", properties.getGrantType());
        params.put("scope", properties.getScope());
        RequestBody body = RequestBody.create(JSON.toJSONString(params), JSON_MEDIA_TYPE);
        String url = EndpointResolver.pickOne(properties.getTokenUrl(), random);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        try (Response response = okHttpClient.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new ConditionException("获取token失败:" + response.message());
            }
            String bodyString = readBody(response.body());
            logTokenRequest(url, params, bodyString);
            JSONObject data = JSON.parseObject(bodyString);
            String accessToken = data.getString("access_token");
            if (StringUtils.isBlank(accessToken)) {
                throw new ConditionException("获取token失败，返回 access_token 为空");
            }
            long expiresIn = data.getLongValue("expires_in");
            return new TokenContext(formatBearer(accessToken), expiresIn);
        } catch (IOException ex) {
            throw new ConditionException("获取token异常", ex);
        }
    }

    private void logTokenRequest(String url, Map<String, Object> params, String bodyString) {
        if (!log.isDebugEnabled()) {
            return;
        }
        Map<String, Object> safeParams = new HashMap<>(params);
        if (safeParams.containsKey("client_secret")) {
            safeParams.put("client_secret", "******");
        }
        log.debug("\n---------------------------------------------------------------\n" +
                        "token服务地址：{}\n请求参数：{}\n响应结果：{}\n---------------------------------------------------------------",
                url, JSON.toJSONString(safeParams), bodyString);
    }

    private String readBody(ResponseBody body) throws IOException {
        if (body == null) {
            throw new ConditionException("token服务返回空响应体");
        }
        return body.string();
    }

    private String formatBearer(String token) {
        return token.startsWith("Bearer") ? token : "Bearer " + token;
    }

    private static final class TokenContext {
        private final String accessToken;
        private final long expireAt;

        private TokenContext(String accessToken, long expiresInSeconds) {
            this.accessToken = accessToken;
            long ttl = expiresInSeconds > 5 ? expiresInSeconds - 5 : 30;
            this.expireAt = Instant.now().plusSeconds(ttl).toEpochMilli();
        }

        private String getAccessToken() {
            return accessToken;
        }

        private boolean isValid() {
            return Instant.now().toEpochMilli() < expireAt;
        }
    }
}

